![]() Calling CFM Code From Classic 68K Code, or
By George Warner |
CONTENTSCalling CFM From Classic CodeShortcuts, Detours & Dead-Ends About Mixed Mode and Routine Descriptors Summary |
There are specific instances when you must call
Code Fragment Manager (CFM) code from classic 68K code -- for example, if your application
cannot be converted to CFM, but you want to be able to use CFM libraries. Or,
you want to add plug-in support to an existing classic 68K application without having to
convert it to CFM68K.
In addition, you may want to use CFM68K to develop an application to run on both 68K and PowerPC computers and use a single FAT library for both environments. Another instance would be developing for OpenDoc, which requires shared library support. Prior to this Technote, only CFM applications could take advantage of OpenDoc. This Technote explains how to add library support to classic code. |
Calling CFM From Classic CodeThe basic steps for calling CFM from classic code are as follows:
|
Determining the Address of the CFM RoutinesThe most common way to determine the address of a CFM routine is to use FindSymbol against a shared library.
The first parameter is a connection ID to a fragment. This can be obtained from a call to GetSharedLibrary, GetDiskFragment or GetMemoryFragment. The second parameter is the name of the symbol, in this case the name of the routine we want to call. The address of the symbol is returned in the third parameter and the last parameter returns the symbols class. |
Creating a Routine DescriptorIn CFM code, you normally use the NewRoutineDescriptor routine to create routine descriptors. The non-CFM version of this is the NewRoutineDescriptorTrap() routine. See the notes on using New[Fat]RoutineDescriptor[Trap] at the end of this Technote.
To create a routine descriptor, you need the address, the procedure information and the architecture of the routine being described. The address was obtained in the first step. The procedure information is based on the calling conventions for the parameters passed to and returned from the routine. The last parameter is the architecture of the routine being called. In the headers, this is defined as the ISAType, which is a combination of two separate 4 bit values, the Instruction Set Architecture (ISA) and Runtime Architecture (RTA). |
Determining the Procedure InformationThe Mixed Mode Manager supports many calling conventions. The most common are stack based for Pascal and C, register based for operating system traps and register dispatched for selector based Toolbox traps. The easiest way to define the procedure information is via a enum: for example, if your routine was defined like this:
Its procedure information would then be defined like this:
|
Determining the ArchitectureEveryone seems to be tempted to use GetCurrentArchitecture here; JUST SAY NO! GetCurrentArchitecture is a macro that returns the architecture of the currently compiling code; in our case, classic. What we want is the architecture of the CFM code that we are calling from our classic code. Remember: the architecture includes the ISA and the RTA. The correct thing to do is to call Gestalt to find out what kind of CPU the code is running on (68K or PPC) and use this to create the routine descriptor with the correct architecture:
|
Calling the Routine DescriptorFrom CFM code you normally use CallUniversalProc to call the routine associated with a routine descriptor. (A Universal Procedure Pointer (UPP) is a pointer to a routine descriptor.) However, CallUniversalProc is only implemented in shared libraries. For compatabality reasons, in classic code UPP's can be treated like ProcPtr's, i.e., they are pointers to executable code. This is because the first two bytes of a routine descriptor is the 68K mixed mode magic ATrap. When we jump here from 68K code, the trap is executed and the Mixed Mode Manager takes over, setting up passed parameters in registers or on the stack, based on the ProcInfo in the routine descriptor: switching the architecture and then jumping to the CFM code. This is how you would call your routine:
Cleaning Up After YourselfDisposeRoutineDescriptorTrap is used to release the memory allocated for routine descriptors by the NewRoutineDescriptorTrap. |
Shortcuts, Detours & Dead-EndsYou may be tempted to use the macro BUILD_ROUTINE_DESCRIPTOR, so that you can build your routine descriptors statically. Unfortunately, this macro expands to include the macro GetCurrentArchitecture whose problem was described in the section above. Another problem with this approach is that the ProcPtr passed to the macro is expected to be a constant at compile-time. One solution to both of these problems is to build your routine descriptors in your CFM library and export them. This way the GetCurrentArchitecture macro returns the correct architecture for the library and the ProcPtr is a compile-time constant. And since these routine descriptors are staticly allocated at compile time, you don't have to worry about disposing them: their memory is released when the library is unloaded. Unfortunately, this only works if you have source to the library you want to connect to. Using BUILD_ROUTINE_DESCRIPTOR to dynamically initialize a routine descriptor is not a good idea. From the classic 68K perspective, the routine descriptor is code being assembled out of data. This can cause problems due to the split caches on 68040 CPUs and some 68K emulator optimizations on PowerPCs. You're trying to execute data but instead are executing old values from the instruction cache. Using NewRoutineDescriptorTrap insures that the instruction cache is flushed for the executable range of the routine descriptor - two bytes. In order to make the connection between classic code and the CFM code as transparent as possible, I like to put all my CFM glue code in its own separate file and use the same API in it as defined for my library (usually by using the library's header file). Each entry point into the library has its own glue routine that declares a static UPP variable initialized to kUnresolvedSymbolAddress. By checking for this initial value, the routine knows when it needs to look up its address in the library and create a routine descriptor. Here's the glue code for the library:
|
Calling CFM from classic code may be necessary for a number of reasons, particularly if you want to take advantage of both the classic and CFM libraries. It may also be the simplest and easiest method of adding plug-in support to an existing 68K or FAT application without having to port the 68K code to CFM68K.
This Technote discusses some straightforward methods you can use to call CFM code from classic code. There are problems, however, that you ought to consider when trying to build routine descriptors for C routines in a shared library.
|
|
Tech Support
Technotes
Previous Technote |
Contents |
Next Technote
Main| Top of Section | What's New | Apple Computer, Inc. | Find It | Feedback | Help